const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*` //匹配标签名 形如 abc-123
const qnameCapture = `((?:${ncname}\\:)?${ncname})` //匹配特殊标签 形如 abc:234 前面的abc:可有可无
const startTagOpen = new RegExp(`^<${qnameCapture}`) // 匹配标签开始 形如 <abc-123 捕获里面的标签名
const startTagClose = /^\s*(\/?)>/ // 匹配标签结束  >
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) // 匹配标签结尾 如 </abc-123> 捕获里面的标签名
const attribute =
  /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ // 匹配属性  形如 id="app"

const ELEMENT_TYPE = 1
const TEXT_TYPE = 3

function createASTElement(tagName, attrs) {
  return {
    tag: tagName,
    type: ELEMENT_TYPE,
    children: [],
    attrs,
    parent: null,
  }
}

let root, currentParent
let stack = []

function handleStartTag({ tagName, attrs }) {
  let element = createASTElement(tagName, attrs)

  if (!root) {
    root = element
  }
  currentParent = element
  stack.push(element)
}

function handleEndTag() {
  let element = stack.pop()
  currentParent = stack[stack.length - 1]

  if (currentParent) {
    element.parent = currentParent
    currentParent.children.push(element)
  }
}

function handleChars(text) {
  text = text.replace(/\s/g, '')

  if (text) {
    currentParent.children.push({
      type: TEXT_TYPE,
      text,
    })
  }
}

export function parse(html) {
  while (html) {
    let textEnd = html.indexOf('<')

    if (textEnd === 0) {
      const startTagMatch = parseStartTag()
      if (startTagMatch) {
        handleStartTag(startTagMatch)
        continue
      }

      const endTagMatch = html.match(endTag)
      if (endTagMatch) {
        advance(endTagMatch[0].length)
        handleEndTag(endTagMatch[1])
        continue
      }
    }

    let text

    if (textEnd >= 0) {
      text = html.slice(0, textEnd)
    }
    if (text) {
      advance(text.length)
      handleChars(text)
    }
  }

  function parseStartTag() {
    const start = html.match(startTagOpen)
    if (start) {
      const match = {
        tagName: start[1],
        attrs: [],
      }
      advance(start[0].length)

      let end, attr
      while (
        !(end = html.match(startTagClose)) &&
        (attr = html.match(attribute))
      ) {
        advance(attr[0].length)
        attr = {
          name: attr[1],
          value: attr[3] || attr[4] || attr[5],
        }
        match.attrs.push(attr)
      }
      if (end) {
        advance(1)
        return match
      }
    }
  }

  function advance(n) {
    html = html.slice(n)
  }

  return root
}
